package es

import (
	"context"
	"encoding/json"
	"fmt"
	"strconv"
	"strings"

	esmodel "mall/common/esmodel/blog"
	"mall/common/globalkey"
	"mall/service/backend/common/errorx"
	"mall/service/backend/common/i18n"
	"mall/service/blog/model"
	"mall/service/blog/rpc/internal/svc"
	"mall/service/blog/rpc/types/blog"

	"github.com/elastic/go-elasticsearch/v7/esapi"
	"github.com/sirupsen/logrus"
	"github.com/zeromicro/go-zero/core/logx"
)

type DiagnoseEntryToEsLogic struct {
	ctx    context.Context
	svcCtx *svc.ServiceContext
	logx.Logger
}

func NewDiagnoseEntryToEsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DiagnoseEntryToEsLogic {
	return &DiagnoseEntryToEsLogic{
		ctx:    ctx,
		svcCtx: svcCtx,
		Logger: logx.WithContext(ctx),
	}
}

// DiagnoseEntryToEs 同步es
func (l *DiagnoseEntryToEsLogic) DiagnoseEntryToEs(in *blog.TmpKBPostListReq) (*blog.TmpKBPostListResp, error) {
	total, err := l.addEntriesToEs(in)
	if err != nil {
		logx.Infow("DiagnoseEntryToEs err", logx.Field("detail", err.Error()))
		return nil, err
	}

	return &blog.TmpKBPostListResp{
		Total: total,
	}, nil
}

func (l *DiagnoseEntryToEsLogic) addEntriesToEs(in *blog.TmpKBPostListReq) (uint64, error) {
	filterMap := make(map[string]map[string][]any)
	filterMap[model.BlogKnowledgeBasisColumns.PostStatus] = map[string][]any{
		"equalTo": {globalkey.PostStatusPublish},
	}
	filterMap[model.BlogKnowledgeBasisColumns.PostParentID] = map[string][]any{
		"equalTo": {0},
	}
	entryList, _, err := l.svcCtx.KBEntry.GetList(l.ctx, model.M{
		"page":     in.Page,
		"pageSize": in.PageSize,
		"orderBy":  model.BlogKnowledgeBasisColumns.ID,
	}, -1, filterMap)
	if err != nil {
		return 0, err
	}

	if len(entryList) == 0 {
		return 0, nil
	}

	var eids []any
	var sids []any
	sidMap := make(map[uint64]struct{})
	for _, v := range entryList {
		eids = append(eids, v.ID)
		if _, ok := sidMap[v.SectionID]; !ok {
			sidMap[v.SectionID] = struct{}{}
			sids = append(sids, v.SectionID)
		}
	}

	idSectionMap := make(map[uint64]*model.BlogTerm)
	if len(sids) > 0 {
		termFilterMap := make(map[string]map[string][]any)
		termFilterMap[model.BlogTermColumns.TermID] = map[string][]any{
			"in": sids,
		}
		termList, _, err := l.svcCtx.Term.GetList(l.ctx, model.M{}, -1, termFilterMap)
		if err != nil {
			return 0, err
		}

		if len(termList) > 0 {
			for _, v := range termList {
				if v.Taxonomy == uint16(globalkey.TermTaxonomyKnowledgeBase) {
					if _, ok := idSectionMap[v.TermID]; !ok {
						idSectionMap[v.TermID] = v
					}
				}
			}
		}
	}

	entryMetaFilterMap := make(map[string]map[string][]any)
	entryMetaFilterMap[model.BlogKnowledgeBaseMetumColumns.KnowledgeBaseID] = map[string][]any{
		"in": eids,
	}
	entryMetaFilterMap[model.BlogKnowledgeBaseMetumColumns.MetaKey] = map[string][]any{
		"in": {"_year", "_gender"},
	}
	orderBy := model.BlogKnowledgeBaseMetumColumns.MetaID + " DESC"
	entryMetaList, _, err := l.svcCtx.KBEntry.GetMeta(l.ctx, model.M{
		"orderBy": orderBy,
	}, -1, entryMetaFilterMap)
	if err != nil {
		return 0, err
	}

	idEntryMetaMap := make(map[uint64][]*model.BlogKnowledgeBaseMetum)
	if len(entryMetaList) > 0 {
		for _, v := range entryMetaList {
			if len(v.MetaKey) > 0 && v.MetaValue.Valid {
				idEntryMetaMap[v.KnowledgeBaseID] = append(idEntryMetaMap[v.KnowledgeBaseID], v)
			}
		}
	}

	relationFilterMap := make(map[string]map[string][]any)
	relationFilterMap[model.BlogKnowledgeBaseRelationshipColumns.FromEntryID] = map[string][]any{
		"in": eids,
	}
	entryRelList, _, err := l.svcCtx.KBEntry.GetRelationshipList(l.ctx, model.M{
		"orderBy": model.BlogKnowledgeBaseRelationshipColumns.ToEntryOrder,
	}, -1, relationFilterMap)
	if err != nil {
		return 0, err
	}

	idEntriesMap := make(map[uint64][]esmodel.EsEntryRelationModel)
	if len(entryRelList) > 0 {
		idRelMap := make(map[uint64][]*model.BlogKnowledgeBaseRelationship)
		tidMap := make(map[uint64]struct{})
		var tids []any
		for _, rel := range entryRelList {
			idRelMap[rel.FromEntryID] = append(idRelMap[rel.FromEntryID], rel)
			if _, ok := tidMap[rel.ToEntryID]; !ok {
				tidMap[rel.ToEntryID] = struct{}{}
				tids = append(tids, rel.ToEntryID)
			}
		}

		// logrus.Info(fmt.Sprintf("entryRelList: %+v", entryRelList))
		// logrus.Info(fmt.Sprintf("tids: %+v", tids))

		if len(tids) > 0 {
			entryFilterMap := make(map[string]map[string][]any)
			entryFilterMap[model.BlogKnowledgeBasisColumns.ID] = map[string][]any{
				"in": tids,
			}
			entryFilterMap[model.BlogKnowledgeBasisColumns.PostStatus] = map[string][]any{
				"equalTo": {globalkey.PostStatusPublish},
			}
			relEntryList, _, err := l.svcCtx.KBEntry.GetList(l.ctx, model.M{}, -1, entryFilterMap)
			if err != nil {
				return 0, err
			}

			relEntryMap := make(map[uint64]*model.BlogKnowledgeBasis)
			for _, v := range relEntryList {
				relEntryMap[v.ID] = v
			}

			for id, rs := range idRelMap {
				entryMap := make(map[uint64]struct{})
				for _, r := range rs {
					if _, ok := entryMap[r.ToEntryID]; ok {
						continue
					}

					entryMap[r.ToEntryID] = struct{}{}

					if e, ok := relEntryMap[r.ToEntryID]; ok {
						idEntriesMap[id] = append(idEntriesMap[id], esmodel.EsEntryRelationModel{
							Id:        e.ID,
							Title:     e.PostTitle,
							Slug:      e.PostName,
							SectionId: e.SectionID,
							GroupId:   uint64(r.ToEntryGroup),
						})
					}
				}
			}
		}
	}

	var res []*esmodel.EntryModel
	for _, v := range entryList {
		var meta []esmodel.EsEntryMetaModel
		if ms, ok := idEntryMetaMap[v.ID]; ok {
			for _, m := range ms {
				if m.MetaKey == "_year" {
					if i, err := strconv.ParseUint(m.MetaValue.String, 10, 64); err == nil || i > 0 {
						meta = append(meta, esmodel.EsEntryMetaModel{
							Key:   "year",
							Value: m.MetaValue.String,
						})
					}
				}
				if m.MetaKey == "_gender" {
					meta = append(meta, esmodel.EsEntryMetaModel{
						Key:   "gender",
						Value: m.MetaValue.String,
					})
				}
			}
		}

		var sections []esmodel.EsEntrySectionModel
		if section, ok := idSectionMap[v.SectionID]; ok {
			sections = append(sections, esmodel.EsEntrySectionModel{
				Id:    section.TermID,
				Title: section.Name,
				Slug:  section.Slug,
			})
		}

		var relations []esmodel.EsEntryRelationModel
		if entries, ok := idEntriesMap[v.ID]; ok {
			relations = entries[:]
		}

		res = append(res, &esmodel.EntryModel{
			Id:        v.ID,
			Title:     v.PostTitle,
			Slug:      v.PostName,
			SectionId: v.SectionID,
			Sections:  sections,
			Meta:      meta,
			Relations: relations,
		})
	}

	//添加es
	if len(res) > 0 {
		for _, v := range res {
			err := l.addEsData(context.Background(), v)
			if err != nil {
				return 0, err
			}
		}
	}

	return uint64(len(res)), nil
}

func (l *DiagnoseEntryToEsLogic) addEsData(ctx context.Context, entry *esmodel.EntryModel) error {
	// Build the request body.
	// Marshal Elasticsearch document struct object to JSON string
	docStr, err := l.jsonStruct(entry)
	if err != nil {
		return err
	}

	id := strconv.FormatUint(entry.Id, 10)
	req := esapi.IndexRequest{
		Index:      "scentrobe-kb-entry",
		DocumentID: id,
		Body:       strings.NewReader(docStr),
		Refresh:    "false",
	}
	// Return an API response object from request
	res, err := req.Do(ctx, l.svcCtx.EsClient)
	if err != nil {
		logrus.Info(fmt.Sprintf("IndexRequest getting response ERROR: %+v", err))
		return err
	}
	defer res.Body.Close()

	if res.IsError() {
		logrus.Info(fmt.Sprintf("[%s] ERROR indexing document ID=%s, %+v", res.Status(), id, res))
		return errorx.NewInvalidArgumentError(i18n.ValidationError)
	}

	// Deserialize the response into a map.
	var resMap map[string]any
	if err = json.NewDecoder(res.Body).Decode(&resMap); err != nil {
		logrus.Info(fmt.Sprintf("Error parsing the response body: %s", err))
		return err
	}

	// logrus.Info(fmt.Sprintf("\nIndexRequest() RESPONSE:"))
	// // Print the response status and indexed document version.
	// logrus.Info(fmt.Sprintf("Status: %v", res.Status()))
	// logrus.Info(fmt.Sprintf("Result: %v", resMap["result"]))
	// logrus.Info(fmt.Sprintf("Version: %d", int(resMap["_version"].(float64))))
	// logrus.Info(fmt.Sprintf("resMap: %+v", resMap))
	// map[_id:40035 _index:scentrobe-kb-entry _primary_term:1 _seq_no:0 _shards:map[failed:0 successful:1 total:2] _type:_doc _version:1 result:created]
	// logrus.Info(fmt.Sprintf("\n"))

	return nil
}

// A function for marshaling structs to JSON string
func (l *DiagnoseEntryToEsLogic) jsonStruct(entry *esmodel.EntryModel) (string, error) {
	// logrus.Info(fmt.Sprintf("jsonStruct docStruct: %+v", entry))

	// Marshal the struct to JSON and check for errors
	b, err := json.Marshal(entry)
	if err != nil {
		logrus.Info(fmt.Sprintf("AddEsData jsonStruct json.Marshal ERROR: %+v", err))
		return "", err
	}

	return string(b), nil
}
